/* ==============================================================================
*
*   Copyright (c) 2010, Pierre Favier
*   All rights reserved.
*
*   NTasking is free software: you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation.
*
*   Redistribution and use in source and binary forms, with or without
*   modification, are permitted provided that the following conditions
*   are met:
*
*   -   Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*   -   Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
*   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
*   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
*   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*   SUCH DAMAGE.
*
* ============================================================================== */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace NTasking.Internals
{

    /// <summary>
    /// The <see cref="Block"/> class supports the implementation of Ada's code blocks composed of
    /// a declarative region, a body and an exception handler. As in Ada, the <see cref="Block"/>
    /// has the added property that all <see cref="Task"/>s declared in the declarative part are
    /// automatically activated when entering the body part.
    /// </summary>
    internal sealed class Block
    {

        #region Fields

        /// <summary>
        /// A lock used to prevent concurrent access to the _environment variable
        /// </summary>
        private static readonly object _globalLock = new object();


        /// <summary>
        /// The list opf task to be activated together
        /// </summary>
        private readonly List<Task> _dependentTasks = new List<Task>();


        /// <summary>
        /// The thread which has started the activator
        /// </summary>
        private readonly ThreadWrapper _thread;


        /// <summary>
        /// A lock used to synchronize the thread which created this instance with dependent tasks
        /// </summary>
        private readonly ManagedLock _activationLock;


        /// <summary>
        /// A lock used to prevent concurrent execution of method Activate by multiple threads
        /// </summary>
        private readonly object _concurrentActivationLock = new object();


        /// <summary>
        /// The name of this instance
        /// </summary>
        private readonly string _name;


        /// <summary>
        /// Store all the <see cref="Task"/>s which have been accessed in this instance's lifetime,
        /// and which have terminated
        /// </summary>
        private readonly List<Task> _terminatedTasks = new List<Task>();


        /// <summary>
        /// Stores all the <see cref="Protected"/> objects declared within this instance's lifetime
        /// </summary>
        private readonly List<Protected> _dependentProtecteds = new List<Protected>();


        /// <summary>
        /// A value indicating whether this <see cref="Block"/> has reached the end of it's body part.
        /// </summary>
        private bool _terminated;


        /// <summary>
        /// Gets or sets a lock used to prevent concurrent access to the DependentTasks property.
        /// </summary>
        /// <value>The lock used to prevent concurrent access to the DependentTasks property.</value>
        private readonly ManagedLock _dependentsLock;


        /// <summary>
        /// Gets or sets a value indicating whether this instance is in the process of terminating all dependent <see cref="Task"/>s
        /// because the instance itself has reached the end of it's body part and all dependent <see cref="Task"/>s are waiting in
        /// a select with an open terminate alternative.
        /// </summary>
        /// <value>
        ///     <c>true</c> if [terminating dependent tasks]; otherwise, <c>false</c>.
        /// </value>
        private bool _terminatingDependentTasks;


        /// <summary>
        /// Gets or sets a boolean indicating whether activation has failed
        /// </summary>
        private bool _failed;


        /// <summary>
        /// Gets or sets the a message detailing which task's activation failed.
        /// </summary>
        /// <value>The failed tasks message.</value>
        private string _failedTasksMessage;


        /// <summary>
        /// Gets or sets the count of tasks beeing activated.
        /// </summary>
        /// <value>The count of tasks beeing activated.</value>
        private int _activationCount;

        #endregion


        #region Properties

        /// <summary>
        /// Gets the priority at which the task activation must be performed.
        /// </summary>
        /// <value>The priority at which the task activation must be performed.</value>
        internal ThreadPriority Priority { get { return _thread.CurrentPriority; } }


        /// <summary>
        /// Gets or sets the top-level <see cref="Block"/> that has been declared in the program
        /// </summary>
        /// <value>The top-level <see cref="Block"/> that has been declared in the program.</value>
        internal static Block Environment { get; private set; }

        #endregion


        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Block"/> class.
        /// </summary>
        /// <param name="name">The name of the <see cref="Block"/>.</param>
        /// <param name="declare">A delegate representing the declarative part of the <see cref="Block"/>.</param>
        /// <param name="begin">A delegate representing the body part of the <see cref="Block"/>.</param>
        /// <param name="exception">A delegate representing the exception handling part of the <see cref="Block"/>.</param>
        /// <param name="isTask">A flag indicating whether this instance is a task's top-level block.</param>
        internal Block(string name, Action declare, Action begin, Func<Exception, bool> exception, bool isTask)
            : this(name, declare, block => { block.Activate(); if (begin != null) begin(); }, exception, isTask)
        {
        }


        /// <summary>
        /// Initializes a new instance of the <see cref="Block"/> class.
        /// </summary>
        /// <param name="name">The name of the <see cref="Block"/>.</param>
        /// <param name="declare">A delegate representing the declarative part of the <see cref="Block"/>.</param>
        /// <param name="begin">A delegate representing the body part of the <see cref="Block"/>.</param>
        /// <param name="exception">A delegate representing the exception handling part of the <see cref="Block"/>.</param>
        /// <param name="isTask">A flag indicating whether this instance is a task's top-level block.</param>
        internal Block(string name, Action declare, Action<Block> begin, Func<Exception, bool> exception, bool isTask)
        {
            bool isEnvironment;

            _activationLock = new ManagedLock("Activator", this);

            _name = string.IsNullOrEmpty(name) ? "anonymous" : name;

            lock (_globalLock)
            {
                isEnvironment = Environment == null;

                _dependentsLock = new ManagedLock("DependentTasks", this);

                if (isEnvironment)
                {
                    // If it is the first Block to be declared, this becomes the Environment block (i.e. the top-level
                    // Block within this application

                    Environment = this;
                    try
                    {
                        Task.CurrentTask.Name = "_main_";
                    }
                    catch (InvalidOperationException)
                    {
                        // Prevent program abortion if the main thread has a name which has been set
                        // before entering the topmost Block
                        // This is particularly important when running unit tests, where each test will
                        // enter a topmost Block in the same Thread
                    }
                }

                _thread = ThreadWrapper.CurrentThread;
            }

            if (isEnvironment)
                // Force creation of the delay task
                Delay.CreateInstance();

            _thread.EnterBlock(this);

            try
            {
                if (declare != null)
                    declare();
                try
                {
                    begin(this);
                }
                catch (ThreadAbortException ex)
                {
                    if (!isTask || exception == null || exception(ex))
                        throw;
                }
                catch (Exception ex)
                {
                    if (exception == null || exception(ex))
                        throw;
                }
            }
            catch (ThreadAbortException ex)
            {
                if (ThreadWrapper.IsIllegalAbort(ex))
                    Errors.FatalError("Using Thread.Abort is not allowed when using the nTasking library.");
            }
            finally
            {
                WaitForEnd();
            }
        }

        #endregion


        #region Methods

        /// <summary>
        /// Adds a new <see cref="Task"/> to the list of objects which depend on this instance.
        /// </summary>
        /// <param name="dependent">The dependent <see cref="Task"/> to be added.</param>
        internal void AddDependent(Task dependent)
        {
            // CONTEXT: this method is executed in the context of the Block

            using (_dependentsLock.Locked())
            {
                _dependentTasks.Add(dependent);
            }
        }


        /// <summary>
        /// Adds a new <see cref="Protected"/> object to the list of objects which depend on this instance.
        /// </summary>
        /// <param name="dependent">The dependent <see cref="Protected"/> object to be added.</param>
        internal void AddDependent(Protected dependent)
        {
            // CONTEXT: this method is executed in the context of the Block

            using (_dependentsLock.Locked())
            {
                _dependentProtecteds.Add(dependent);
            }
        }


        /// <summary>
        /// Removes a <see cref="Protected"/> object from the list of objects which depend on this instance.
        /// </summary>
        /// <param name="dependent">The dependent <see cref="Protected"/> object to be removed.</param>
        internal void RemoveDependent(Protected dependent)
        {
            // CONTEXT: this method is executed in the context of the dependent task, not in the context of the Block

            using (_dependentsLock.Locked())
            {
                _dependentProtecteds.Remove(dependent);
            }
        }


        /// <summary>
        /// Removes a <see cref="Task"/> from the list of objects which depend on this instance.
        /// </summary>
        /// <param name="dependent">The dependent <see cref="Task"/> to be removed.</param>
        internal void RemoveDependent(Task dependent)
        {
            // CONTEXT: this method is executed in the context of the dependent task, not in the context of the Block

            using (ThreadWrapper.CurrentThread.NoAbort())
            {
                Tracer.Trace("Terminating dependent task " + dependent);

                using (_dependentsLock.Locked())
                {
                    // Move the task from the list of dependent tasks to the list of terminated tasks
                    _terminatedTasks.Add(dependent);
                    _dependentTasks.Remove(dependent);

                    if (!_terminated) return;

                    // If this instance is terminated (i.e. has reached the end of it's body part)
                    // either wake-up the thread in which this instance was declared when there is no
                    // more dependent tasks, or check if all remaining dependent tasks can be terminated

                    if (_dependentTasks.Count == 0)
                    {
                        Tracer.Trace("This was the last dependent task " + dependent);
                        _dependentsLock.Pulse();
                    }
                    else
                    {
                        Tracer.Trace("This was not the last dependent task " + dependent +
                                     " trying to terminate other dependent tasks");
                        TerminateDependentTasksIfPossible(null);
                    }
                }
            }
        }


        /// <summary>
        /// Request that the calling <see cref="Task"/> be suspended or terminated depending on whether all other
        /// dependent <see cref="Task"/>s are also waiting on a terminate alternative or not.
        /// </summary>
        /// <param name="task">The <see cref="Task"/> to be suspended or terminated.</param>
        /// <param name="suspendAction">The action to be performed to suspend the calling <see cref="Task"/>.</param>
        /// <returns></returns>
        internal RendezVous SuspendOrTerminate(Task task, Func<RendezVous> suspendAction)
        {
            // CONTEXT: this method is executed in the context of the calling task passed as a parameter,
            // not in the context of the Block

            using (_dependentsLock.Locked())
            {
                if (TerminateDependentTasksIfPossible(task))
                {
                    // If all other dependent tasks are waiting on a terminate alternative
                    // then terminate the caller

                    Tracer.Trace("SuspendOrTerminate can Terminate");
                    task.Terminate();
                    // Will never reach this point
                }

                // Make sure the task owning the current Block can re-acquire the lock
                // on DependentTasks
                using (_dependentsLock.Unlocked())
                {
                    // If not all dependent tasks are waiting on a terminate alternative
                    // then suspend the caller task until termination or until an entry call is made

                    Tracer.Trace("SuspendOrTerminate will Suspend");
                    return suspendAction();
                }
            }
        }


        /// <summary>
        /// Check if all dependent <see cref="Task"/>s are waiting on a terminate alternative and terminate them.
        /// </summary>
        /// <param name="task">The <see cref="Task"/> in which context this call is executed.</param>
        /// <returns></returns>
        private bool TerminateDependentTasksIfPossible(Task task)
        {
            // CONTEXT: this method is executed in the context of the calling task passed as a parameter,
            // not in the context of the Block, unless the task passed in parameter is null

            if (!_terminated)
            {
                // This instance has not yet reached the end of it's body part
                Tracer.Trace("Can't terminate dependent tasks because Block is not finished or has dependent pseudo tasks");
                return false;
            }

            if (_terminatingDependentTasks)
            {
                // Termination of dependent tasks is already in progress
                Tracer.Trace("Can't terminate dependent tasks because already terminating them");
                return true;
            }

            if (_dependentTasks.Any(dependent => dependent != task && !dependent.SuspendedWithTerminate))
            {
                // Not all other dependent tasks are waiting on a terminate alternative
                Tracer.Trace("Can't terminate dependent tasks because not all waiting on terminate");
                return false;
            }

            _terminatingDependentTasks = true;

            Tracer.Trace("Starting terminate dependent tasks");

            // Terminate all dependent Tasks (except the Calling Task)
            foreach (Task dependent in _dependentTasks.Where(dependent => dependent != task && dependent.TaskStatus != TaskStatus.Inactive))
            {
                dependent.Terminate();
            }

            return true;
        }


        /// <summary>
        /// Aborts all <see cref="Task"/>s dependent of this instance. This occurs when the task in which
        /// this instance is declred has been aborted.
        /// </summary>
        internal void AbortDependentTasks()
        {
            // CONTEXT: this method is executed in the context of the Block

            Tracer.Trace("Aborting dependents");

            using (_dependentsLock.Locked())
            {
                foreach (Task dependent in _dependentTasks.Where(d => d.Callable))
                {
                    //Console.WriteLine(Internal + "   aborting dependent " + dependent);
                    Tracer.Trace("   aborting dependent " + dependent);
                    dependent.Abort();
                }
            }
        }


        /// <summary>
        /// Terminates all <see cref="Task"/>s dependent which have not yet been started.
        /// </summary>
        private void TerminateUnstartedTasks()
        {
            // CONTEXT: this method is executed in the context of the Block

            Tracer.Trace("Terminating unstarted tasks");

            using (_dependentsLock.Locked())
            {
                foreach (Task dependent in _dependentTasks.ToArray().Where(d => d.TaskStatus == TaskStatus.Inactive))
                {
                    Tracer.Trace("   terminating unstarted " + dependent);
                    dependent.Abort();
                }
            }
        }


        /// <summary>
        /// Disposes all the <see cref="Protected"/> objects dependent of this instance.
        /// </summary>
        private void DisposeDependentProtecteds()
        {
            // CONTEXT: this method is executed in the context of the Block

            Tracer.Trace("Disposing Block's dependent Protecteds");

            Protected[] dependents = _dependentProtecteds.ToArray();

            foreach (Protected prot in dependents)
                prot.Dispose();
        }


        /// <summary>
        /// Wait until all dependent <see cref="Task"/>s are terminated.
        /// </summary>
        private void WaitForEnd()
        {
            // CONTEXT: this method is executed in the context of the Block

            lock (_concurrentActivationLock)
            {
                using (_dependentsLock.Locked())
                {
                    Tracer.Trace("Disposing Block");

                    // Mark this instance as terminated
                    _terminated = true;

                    TerminateUnstartedTasks();

                    TerminateDependentTasksIfPossible(null);

                    // Wait until all dependent tasks are terminated
                    Tracer.Trace("Waiting for last dependent to die");
                    while (_dependentTasks.Count > 0)
                        _dependentsLock.WaitLock();

                    DisposeDependentProtecteds();
                }

                _dependentTasks.Clear();
                _dependentProtecteds.Clear();
            }

            Tracer.Trace("All dependent terminated");

            Tracer.Trace("Joining all dependents");

            foreach (Task task in _terminatedTasks)
                task.Join();

            Tracer.Trace("Finished disposing Block");

            _thread.LeaveBlock(this);

            lock (_globalLock)
                if (ReferenceEquals(this, Environment))
                {
                    // If this block was the top-level one, clear the environment
                    Environment = null;
                    Delay.ClearInstance();
                    ThreadWrapper.CurrentThread = null;
                    Task.CurrentTask = null;
                    //Task.CurrentRealTask = null;
                }

            GC.SuppressFinalize(this);
        }


        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            if (_name == null)
                return _thread.ToString();

            return _name + "@" + _thread;
        }


        internal void Activate()
        {
            // Check that this instance can be blocked since task activation is an Ada
            // 'potentially blocking operation'
            if (!ThreadWrapper.CurrentThread.CanBlock)
                throw Errors.PotentiallyBlockingOperationNotAllowed("Task activation");

            lock (_concurrentActivationLock)
            {
                Activate(_dependentTasks);
            }
        }


        internal void Activate(Task task)
        {
            // Check that this instance can be blocked since task activation is an Ada
            // 'potentially blocking operation'
            if (!ThreadWrapper.CurrentThread.CanBlock)
                throw Errors.PotentiallyBlockingOperationNotAllowed("Task activation");

            lock (_concurrentActivationLock)
            {
                if (task.TaskStatus != TaskStatus.Inactive)
                    throw Errors.IncorrectTaskStatus(task);

                Activate(new[] { task });
            }
        }


        /// <summary>
        /// Activates the tasks and wait until all tasks are activated.
        /// </summary>
        private void Activate(IEnumerable<Task> tasks)
        {
            _failedTasksMessage = "";

            using (_thread.NoAbort())
            {
                _activationCount = 0;

                using (_activationLock.Locked())
                {
                    foreach (Task task in tasks.Where(task => task.TaskStatus == TaskStatus.Inactive))
                    {
                        _activationCount += 1;
                        Tracer.Trace("Activating task " + task);
                        task.StartActivation();
                    }

                    while (_activationCount > 0)
                        _activationLock.WaitLock();
                }

                if (_failed)
                {
                    Tracer.Trace("Some tasks activation failed");
                    throw Errors.ActivationFailed(_failedTasksMessage);
                }

                Tracer.Trace("All tasks successfully activated");
            }
        }


        /// <summary>
        /// Signal to this instance that the given task's activation has succeded.
        /// </summary>
        /// <param name="task">The task which activation has succeded.</param>
        internal void ActivationSucceded(Task task)
        {
            using (task.Thread.NoAbort())
            {
                using (_activationLock.Locked())
                {
                    Tracer.Trace("Activation of task " + task + " succeded");

                    _activationCount -= 1;

                    if (_activationCount == 0)
                        _activationLock.Pulse();
                }
            }
        }


        /// <summary>
        /// Signal to this instance that the given task's activation has failed.
        /// </summary>
        /// <param name="task">The task which activation has failed.</param>
        internal void ActivationFailed(Task task)
        {
            using (task.Thread.NoAbort())
            {
                using (_activationLock.Locked())
                {
                    Tracer.Trace("Activation of task " + task + " failed");

                    _failedTasksMessage += string.Format("\\n    Activation of {0} failed", task);

                    _failed = true;

                    _activationCount -= 1;

                    if (_activationCount == 0)
                        _activationLock.Pulse();
                }
            }
        }

        #endregion

    }

}

